/*
创建的索引存储位置从200开始
索引的创建方式为为每一个块建立一个索引项，存储当前块的第一个值（最小值）和块号
索引存储位置为200~204
    选择结果存储位置为220
*/
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "extmem.h"
#include "t2.h"
// 将块的信息写入索引，rblkNo为读入块号，wblkNo为写出块号，rblk为块的起始地址，
// wblk为写入块的起始地址，p为块当前写到的位置
// （实际上写数据是开始的第一个C值）
// 返回值为写入是否成功了
int writeBlockIndex(int rblkNo,int *wblkNo,unsigned char *rblk,unsigned char *wblk,int *wp,Buffer *buf);

// 根据索引找到开始的块
// index 为索引开始的位置
int findBlockByIndex(int indexBlkNo,Buffer *buf,int selNum);

// 为关系S创建索引，并且是根据其第一个元素创建
// wf为索引写入的起始地址，rf为需要建立表的起始地址，length为表的长度
// 若成功地创建索引，返回值为创建索引的长度,创建失败则返回-1
int createIndex(int wf,int rf,int length,Buffer *buf);

int getNextIndexValue(unsigned char** blk,int *rp,int *indexBlkNo,Buffer *buf);

int getBlockNum(unsigned char *blk,int p);

// 返回值为找到的元组数量
 

int main(int argc, char const *argv[])
{
    Buffer buf;
    if (!initBuffer(520, 64, &buf))
    {
        perror("Buffer Initialization Failed!\n");
        return -1;
    }
    int count;
    createIndex(200,132,32,&buf);
    printf("\ncreat Index Buf IO:%ld\n",buf.numIO);
    printf("\n--------------\n");
    buf.numIO=0;
    count=selectByNum(130,220,200,&buf);
    printf("\n--------------\n");
    printf("\nfound count:=%d\n",count);
    printf("\nselect with Index Buf IO:%ld\n",buf.numIO);
}

int createIndex(int wf,int rf,int length,Buffer *buf)
{
    int wfStart=wf;
    // 申请写块
    unsigned char *wblk=getNewBlockInBuffer(buf);
    if (wblk==NULL)
    {
        perror("error in getWblk");
        return -1;
    }
    int wp=0;
    // 遍历关系中所有的块，并给每个块创建一个索引
    for (int i = rf; i < rf+length; i++)
    {
        unsigned char *rblk=readBlockFromDisk(i,buf);
        if (!rblk)
        {
            perror("error in read blk");
            return -1;
        }
        if(!writeBlockIndex(i,&wf,rblk,wblk,&wp,buf))
        {
            return -1;
        }
        freeBlockInBuffer(rblk,buf);
    }
    
    // 资源释放的同时将最后的信息写入buf中（清空缓存）
    // 错误的话会返回-1
    // 写入前需要将剩下的以往残留数据清零
    memset(wblk+wp,0,sizeof(char)*(64-wp));
    if(writeBlockToDisk(wblk,wf,buf))
    {
        return -1;
    }   
    return wf-wfStart+1;
}

// index的内容为先数值后块号
int writeBlockIndex(int rblkNo,int *wblkNo,unsigned char *rblk,unsigned char *wblk,int *wp,Buffer *buf)
{
    char blkStr[5];
    itoa(rblkNo,blkStr);
    for (int i = 0; i < 4; i++)
    {
        *(wblk+(*wp)++)=*(rblk+i);
    }
    for (int i = 0; i < 4; i++)
    {
        *(wblk+(*wp)++)=blkStr[i];
    }
    // 当块满了写入内存
    if (*wp==56)
    {
        char next[5];
        itoa((*wblkNo)+1,next);
        // printf("\nis here?%s\n",next);
        int t=0;
        while (next[t]!='\0')
        {
            *(wblk+*wp)=next[t];
            (*wp)++;
            t++;
        }
        
        
        if (writeBlockToDisk(wblk, (*wblkNo)++, buf) != 0)
        {
            perror("Writing Block Failed!\n");
            return -1;
        }
        wblk = getNewBlockInBuffer(buf);
        // 重新从0开始写数据，就相当于使用了free
        // api里的free相当于告知buf当前块已不再需要使用
        *wp=0;
    }
    return 1;
    
}

// 获得下一个索引值
// 这里的blk用的是双重指针，因为指向块的指针也需要在函数里面改变
int getNextIndexValue(unsigned char** blk,int *rp,int *indexBlkNo,Buffer *buf)
{
    char valStr[4];
    for (int i = 0; i < 3; i++)
    {
        // 注意此处rp的累加的写法，一定要加括号，错了几次了
        valStr[i]=*(*blk+(*rp)++);
    }
    valStr[3]='\0';
    // 移动到下一个索引的位置
    (*rp)+=5;    
    int val=atoi(valStr);
    // 在valStr为null时，atoi函数会将valStr返回为0，代表已经没有下一个索引元素了
    if (val==0)
    {
        return -1;
    }
    
    if (*rp == 56)
    {
        freeBlockInBuffer(*blk,buf);
        *blk=readBlockFromDisk(++(*indexBlkNo),buf);
        if (*blk==NULL)
        {
            perror("error in read IndexBlock Or none next block");
            return -1;
        }                              
        *rp=0;
    }
    return val;
}

// 根据索引块的起始地址和偏移地址返回块号
int getBlockNum(unsigned char *blk,int p)
{
    p+=4;
    char numStr[5];
    for (int i = 0; i < 4; i++)
    {
        numStr[i]=*(blk+p++);
    }
    return atoi(numStr);
}

// 找的是要开始搜索元组的块号
int findBlockByIndex(int indexBlkNo,Buffer *buf,int selNum)
{
    unsigned char *indexBlk=readBlockFromDisk(indexBlkNo,buf);
    int rp=0;
    if (!indexBlk)
    {
        perror("error in read Index First Block");
        return -1;
    }
    
    int val=getNextIndexValue(&indexBlk,&rp,&indexBlkNo,buf);
    if (val>selNum)
    {
        return -1;
    }
    unsigned char *indexBlk_pre=indexBlk;
    int rp_pre=rp-8;
    int val_pre=val;

// 遍历地找一个块，这个块的起始元素应该小于selNum而他下一个块的起始元素大于等于selNum
    while ((val=getNextIndexValue(&indexBlk,&rp,&indexBlkNo,buf))!=-1)
    {
        if (val_pre<selNum && val>=selNum)
        {
            return getBlockNum(indexBlk_pre,rp_pre);
        }
        // rp跳得太快了，快了两个节拍，所以这里跨块的时候需要等一个节拍
        if (rp==0)
        {
            rp_pre=48;
        }
        else
        {
            rp_pre=rp-8;
            indexBlk_pre=indexBlk;
        }
        
        val_pre=val;
    }
    // 找遍所有的索引都找不到，返回-1
    return -1;

    // do
    // {
    //     if (val_pre)
    //     {
    //         /* code */
    //     }
        
        
    //     rp_pre=rp;
    //     indexBlk_pre=indexBlk;
    // } while (getNextIndexvalue(indexBlkNo,&rp,&indexBlkNo,buf));
}

// 根据S关系的C来筛选元组，并写入块中
// num为C的值，wf为找到的元组开始写入的硬盘块号，indexBlockNum为index开始的块号
int selectByNum(int num,int wf,int indexBlockNum,Buffer *buf)
{
    int count=0;
    // 在索引中找到合适的开始块
    int blkNo=findBlockByIndex(indexBlockNum,buf,num);
    unsigned char *rblk=readBlockFromDisk(blkNo,buf);
    unsigned char *wblk=getNewBlockInBuffer(buf);
    int rp=0;
    int wp=0;
    int X,Y;
    while (1)
    {
        getValueByp(&X,&Y,rp,rblk);
        if (X<num)
        {
            rp+=8;
            if (rp==56)
            {
                freeBlockInBuffer(rblk,buf);
                rblk=readBlockFromDisk(++blkNo,buf);
                rp=0;
            }
        }
        else if (X==num)
        {
            do
            {
                count++;
                printf("\nfound X:%d Y:%d\n",X,Y);
                if(writeData(&rp,&wp,rblk,wblk,&wf,buf))
                {
                    rp=0;
                    freeBlockInBuffer(rblk,buf);
                    rblk=readBlockFromDisk(++blkNo,buf);
                }
                getValueByp(&X,&Y,rp,rblk);
            } while (X==num);
            memset(wblk+wp,0,sizeof(char)*64-wp);
            writeBlockToDisk(wblk,wf,buf);
            break;
        }
        // X>num
        else
        {
            break;
        }
    }
    return count;
}
